#ifndef NIGHT_TENSOR_COMPOSITE
#define NIGHT_TENSOR_COMPOSITE

#include <functional>

#include "core.hpp"
#include "shape.hpp"
#include "expr.hpp"

namespace night {

namespace tensor {

template< typename E, typename R, typename S >
struct reference_tensor : Expr< reference_tensor<E,R,S>, R, S > 
{
	typedef typename reference_tensor::result_type value_type;

	typename E::value_type expr;

	reference_tensor ( typename E::param_type src ) : expr (src) {}

	template< typename Shape >
	value_type at ( Shape const & s ) const	{ return this->expr (s); }

};

template< template<typename> class Op, typename LE, typename LR, typename LS, typename RE, typename RR, typename RS >
struct composite_tensor : Expr< composite_tensor<Op,LE,LR,LS,RE,RR,RS>, typename detail::remove_cref<LR>::type, typename join<LS,RS>::type > 
{
	typedef typename composite_tensor::result_type value_type;

	typename LE::value_type lhs;
	typename RE::value_type rhs;

	composite_tensor ( typename LE::param_type x, typename RE::param_type y ) : lhs (x), rhs (y) {}

	template< typename Shape >
	value_type at ( Shape const & s ) const {
		return Op<value_type>() ( this->lhs.at ( LS(s) ), this->rhs.at ( RS(s) ) ); 
	}
};

template< template<typename> class Op, typename RE, typename RR, typename RS > 
struct unary_tensor : Expr< unary_tensor<Op,RE,RR,RS>, typename detail::remove_cref<RR>::type, RS > 
{
	typedef typename unary_tensor::result_type value_type;

	typename RE::value_type rhs;

	unary_tensor ( typename RE::param_type x ) : rhs (x) {}

	template< typename Shape >
	value_type at ( Shape const & s ) const { return Op<value_type>() ( this->rhs.at ( s ) ); }

};

template< template<typename> class Op, typename LE, typename LR, typename LS, typename RE, typename RR, typename RS > 
struct binary_tensor : Expr< binary_tensor<Op,LE,LR,LS,RE,RR,RS>, typename detail::remove_cref<LR>::type, typename detail::if_c< detail::is_same< LS, shape<> >::value, RS, LS >::type > 
{
	typedef typename binary_tensor::result_type value_type;

	typename LE::value_type lhs;
	typename RE::value_type rhs;

	binary_tensor ( typename LE::param_type x, typename RE::param_type y ) : lhs (x), rhs (y) {}

	template< typename Shape >
	value_type at ( Shape const & s ) const { 
		return Op<value_type>() ( this->lhs ( LS(s) ), this->rhs ( RS(s) ) );
	}

};

} // end namespace night::tensor

} // end namespace nigth

#endif
